﻿using Basic.EventBus.Attributes;
using Basic.EventBus.Eventbus;
using Microsoft.Extensions.Logging;
using RabbitMQ.Client;

namespace Basic.EventBus.EventBusRabbitMQ;

internal class RabbitMQProducer : IEventPublisher
{
    private ILogger<RabbitMQProducer> Logger;
    private RabbitMQConnections Connections;
    private Dictionary<string, ChannelInfo> ChannelDict = new Dictionary<string, ChannelInfo>();

    public RabbitMQProducer(ILogger<RabbitMQProducer> logger, RabbitMQConnections connections)
    {
        Logger = logger;
        Connections = connections;
    }

    private void Publish(byte[] data, EventTopicAttribute attribute, ChannelInfo channelInfo)
    {
        channelInfo.Channel.BasicPublish(attribute.Group, attribute.Key, false, channelInfo.BasicProperties, data);
    }

    public Task<bool> PublishAsync(byte[] data, EventTopicAttribute attribute)
    {
        var channelInfo = GetChannelInfo(attribute);
        if (channelInfo != null)
        {
            Publish(data, attribute, channelInfo);
            return Task.FromResult(true);
        }
        return Task.FromResult(false);
    }

    public Task<bool> PublishAsync(IEventData eventData)
    {
        bool success = false;
        var attribute = EventTopicAttribute.GetCurrentAttribute(eventData.GetType());
        if (attribute != null)
        {
            var channelInfo = GetChannelInfo(attribute);
            if (channelInfo != null)
            {
                Publish(eventData.Serialize(), attribute, channelInfo);
                success = true;
            }
        }
        return Task.FromResult(success);
    }

    public Task<int> PublishAsync(List<IEventData> eventDatas)
    {
        if (eventDatas.Count > 0)
        {
            var attribute = EventTopicAttribute.GetCurrentAttribute(eventDatas[0].GetType());
            if (attribute != null)
            {
                var channelInfo = GetChannelInfo(attribute);
                if (channelInfo != null)
                {
                    foreach (var eventData in eventDatas)
                    {
                        Publish(eventData.Serialize(), attribute, channelInfo);
                    }
                }
            }
        }
        return Task.FromResult(eventDatas.Count);
    }

    private ChannelInfo GetChannelInfo(EventTopicAttribute attribute)
    {
        if (attribute == null) return null;

        ChannelInfo channelInfo = null;
        if (!ChannelDict.TryGetValue(attribute.Topic, out channelInfo))
            lock (ChannelDict)
                if (!ChannelDict.TryGetValue(attribute.Topic, out channelInfo))
                {
                    IConnection connection = null;
                    try
                    {
                        connection = Connections.GetConnection();
                        IModel channel = connection.CreateModel();
                        channelInfo = new ChannelInfo(attribute.Topic, channel);
                        ChannelDict.TryAdd(attribute.Topic, channelInfo);
                    }
                    catch (Exception ex) { Logger.LogError(ex, "CreateModel()->Error:"); }
                    finally { Connections.Return(connection); }
                }

        return channelInfo;
    }

    #region 通道信息
    internal class ChannelInfo
    {
        public ChannelInfo(string group, IModel channel)
        {
            Group = group;
            Channel = channel;
            BasicProperties = channel.CreateBasicProperties();
        }

        public string Group { get; private set; }

        public IModel Channel { get; private set; }

        public IBasicProperties BasicProperties { get; private set; }
    }
    #endregion

    #region Dispose
    private bool disposedValue;

    protected virtual void Dispose(bool disposing)
    {
        if (!disposedValue)
        {
            if (disposing)
            {
                // TODO: 释放托管状态(托管对象)
            }
            // TODO: 将大型字段设置为 null
            disposedValue = true;
        }
    }

    public void Dispose()
    {
        // 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中
        Dispose(disposing: true);
        GC.SuppressFinalize(this);
    }

    #endregion

}